package org.huwei.data.manager;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.huwei.util.ClassFileUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;

import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 	数据源管理器制造工厂
 * 	读取{@link DataSourceManagerFacotryProperties#getDataSourceInterfacePackagePath()}包路径所定义的接口，
 * 	并从spring容器中加载接口的示例对象，并生成其管理对象；
 * @author huwei
 */
@Slf4j
public class DataSourceManagerFacotry {
	/**
	 * spring 应用环境，用于获取数据源接口的所有实现类
	 */
	private ApplicationContext context;
	/**
	 * data manager配置参数
	 */
	private DataSourceManagerFacotryProperties properties;
	/**
	 * 数据源接口类集合
	 */
	private Set<Class<?>> dataSourceInterfaceClassSet;
	/**
	 * 数据源接口与实现类映射
	 */
	private Map<Class<?> ,List<DataSourceOperation>> dataSouceMap = new HashMap<Class<?>, List<DataSourceOperation>>();
	/**
	 * 数据源接口与管理对象的映射
	 */
	private Map<Class<?> ,DataSourceManager<?>> dataSourceManagerMap = new HashMap<Class<?>, DataSourceManager<?>>();
	
	/**
	 * 用户自定义数据源执行策略、数据源过滤方式、以及数据源的执行优先级；
	 */
	private DataSourceManagementStrategy managementStrategy;
	private DataSourceFilter dataSourceFilter;
	private DataSourceOrderProperites dataSourceOrderProperites;
	
	public DataSourceManagerFacotry(ApplicationContext context ,String dataSourceInterfacePackagePath) {
		this(context ,new DataSourceManagerFacotryProperties(dataSourceInterfacePackagePath));
	}
	
	public DataSourceManagerFacotry(ApplicationContext context ,DataSourceManagerFacotryProperties properties) {
		this(context ,properties ,null ,null ,null);
	}
	
	public DataSourceManagerFacotry(ApplicationContext context ,DataSourceManagerFacotryProperties properties 
			, DataSourceManagementStrategy managementStrategy
			, DataSourceFilter dataSourceFilter
			, DataSourceOrderProperites dataSourceOrderProperites) {
		this.context = context;
		this.properties = properties;
		this.dataSourceFilter = dataSourceFilter;
		this.managementStrategy = managementStrategy;
		this.dataSourceOrderProperites = dataSourceOrderProperites;
		loadDataSourceInterfaceClass();
		findDataSourceImpl();
		generateDataManager();
	}
	
	/**
	 * 	获取给定Class类的数据源管理对象 ，该Class类最好是{@link DataSourceManagerFacotryProperties#getDataSourceInterfacePackagePath()}包路径下所包含的数据源接口的类类型，
	 * 	这样获取到的{@link DataSourceManager}对象才具有真实可操作的数据源；
	 * 	当然，如果不是也没有关系，方法的返回值将是一个{@link DefaultDataSourceManager}对象，其available()方法将返回false，这需要用户自行判断；
	 * @param cls {@link DataSourceManagerFacotryProperties#getDataSourceInterfacePackagePath()}包路径下所包含的数据源接口
	 * @return 数据源管理对象
	 */
	@SuppressWarnings("unchecked")
	public <T> DataSourceManager<T> getDataManager(Class<? extends T> cls) {
		DataSourceManager<T> dsm = (DataSourceManager<T>) dataSourceManagerMap.get(cls);
		return Objects.nonNull(dsm) ? dsm : new DefaultDataSourceManager<T>(null ,null);
	}
	
	private void findDataSourceImpl() {
		for(Class<?> cls : dataSourceInterfaceClassSet) {
			Map<String, ?> maps = context.getBeansOfType(cls);
			log.info("----> The implementation class size : " + maps.size() +" of " + cls.getName());
			List<DataSourceOperation> dataSources = switchSpringBean(maps ,cls);
			//initialize order priority
			initializeOrderPriority(dataSources ,cls.getAnnotation(Order.class));
			//filter bean
			dataSources = dataSourceListFilter(dataSources);
			if(!dataSources.isEmpty()) {
				Collections.sort(dataSources);
				dataSouceMap.put(cls, dataSources);
			}
		}
	}
	
	private void initializeOrderPriority(List<DataSourceOperation> dataSources ,Order superOrderClass) {
		for(int i = dataSources.size() -1 ;i >= 0 ;--i) {
			initializeOrderPriority(dataSources.get(i) ,superOrderClass);
		}
	}
	
	private void initializeOrderPriority(DataSourceOperation dataSource ,Order superOrderClass) {
		Integer order = null;
		if(Objects.nonNull(dataSource.instance().getClass().getAnnotation(Order.class))) {
			 order = dataSource.instance().getClass().getAnnotation(Order.class).value();
		}
		else if(Objects.nonNull(dataSourceOrderProperites) 
				&& Objects.nonNull(order = dataSourceOrderProperites.priority(dataSource.springKey()))) {
		}
		else if(Objects.nonNull(order = findSuperOrderClass((dataSource.interfaceClass())))) {
		}
		else if(Objects.nonNull(superOrderClass)){
			order = superOrderClass.value();
		}
		((DataSource)dataSource).order = order;
	}

	private List<DataSourceOperation> dataSourceListFilter(List<DataSourceOperation> dataSources) {
		List<DataSourceOperation> result = dataSources;
		if(Objects.isNull(result) || result.isEmpty()) {
			return new ArrayList<DataSourceOperation>();
		}
		for(int i = result.size() -1;i >= 0 ;--i) {
			DataSourceOperation ds = result.get(i);
			if(dataSourceFilter(ds)) {
				log.debug("the data source"+ds.instance().getClass().getName()+" will be  intercept!!" );
				result.remove(i);
			}
		}
		return result;
	}

	private List<DataSourceOperation> switchSpringBean(Map<String , ?> springBeanMap ,Class<?> interfaceClass){
		List<DataSourceOperation> dataSources = new ArrayList<DataSourceOperation>();
		for(Map.Entry<String, ?> entry : springBeanMap.entrySet()) {
			dataSources.add(switchSpringBean(entry.getKey(), entry.getValue() ,interfaceClass));
		}
		return dataSources;
	}
	
	private DataSourceOperation switchSpringBean(String springKey ,Object bean ,Class<?> interfaceClass ) {
		return new DataSource(springKey, bean,interfaceClass);
	}

	private Integer findSuperOrderClass(Class<?> cls) {
		Class<?>[] clss = cls.getInterfaces();
		for(int i = clss.length -1 ;i >= 0 ;--i) {
			Order order = clss[i].getAnnotation(Order.class);
			if(Objects.nonNull(order)) {
				return order.value();
			}
		}
		return null;
	}

	private void loadDataSourceInterfaceClass() {
		if(Objects.isNull(properties.getDataSourceInterfacePackagePath())) {
			log.info("----> data.management.interfacePackageName is not define ,and data manager is unavaiable!!");
			return;
		}
		dataSourceInterfaceClassSet = ClassFileUtil.getClasses(properties.getDataSourceInterfacePackagePath());
		log.info("----> data interface class size : " + dataSourceInterfaceClassSet.size() +" in package <" + properties.getDataSourceInterfacePackagePath() + ">");
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void generateDataManager() {
		for(Map.Entry<Class<?>, List<DataSourceOperation>> entry : dataSouceMap.entrySet()) {
			dataSourceManagerMap.put(entry.getKey(),
					new DefaultDataSourceManager(new DataSourceProxy(entry.getKey(), entry.getValue()) 
							,dataSouceMap.get(entry.getKey())));
		}
		
		DataSourceUtil.addManager(dataSourceManagerMap);
	}
	
	private boolean dataSourceFilter(DataSourceOperation ds) {
		return Objects.isNull(dataSourceFilter) ? FactoryUtil.DATA_SOURCE_FILTER.filter(ds) : dataSourceFilter.filter(ds);
	}
	
	private class DataSourceProxy implements MethodInterceptor {
		protected List<DataSourceOperation> dataSourceList;
		protected Class<?> target;
		public DataSourceProxy(Class<?> target ,List<DataSourceOperation> dataSourceList) {
			this.target = target;
			this.dataSourceList = dataSourceList;
		}
		public Object getProxy() {
			return FactoryUtil.dynamicProxy(target, this);
		}
		@Override
		public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
			List<DataSourceOperation> dso = new ArrayList<DataSourceOperation>();
			for(int i = 0 ,len = dataSourceList.size() ;i < len ;++i) {
				DataSourceOperation dataSource = dataSourceList.get(i);
				((DataSource)dataSource).args = args;
				((DataSource)dataSource).method = matchMethod(method ,dataSource);
				dso.add(dataSource);
			}
			return Objects.isNull(managementStrategy) ? FactoryUtil.DEFAULT_MANAGEMENT_STRATEGY.handle(dso) : managementStrategy.handle(dso);
		}
		
		private Method matchMethod(Method method ,DataSourceOperation ds) {
			Method[] methods = ds.instance().getClass().getMethods();
			for(Method m : methods) {
				if(m.getName().equals(method.getName())
						&& m.getReturnType().equals(method.getReturnType()) 
						&& Arrays.equals(method.getParameterTypes(), m.getParameterTypes())) {
					return m;
				}
			}
			return null;
		}
	}
	
	static private class FactoryUtil {
		/**
		 *	 默认的数据源过滤工具
		 */
		final static private DataSourceFilter DATA_SOURCE_FILTER = new DataSourceFilter() {
		};
		/**
		 *	默认的管理策略
		 */
		final static private DataSourceManagementStrategy DEFAULT_MANAGEMENT_STRATEGY = new DataSourceManagementStrategy() {
		};
		
		final static public Object dynamicProxy(Class<?> superClass , MethodInterceptor methodInterceptor) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(superClass);
			enhancer.setCallback(methodInterceptor);
			return  enhancer.create();
		}
	}
	
	private class DefaultDataSourceManager<T> implements DataSourceManager<T>{
		private DataSourceProxy cglibDataSourceProxy;
		private List<DataSourceOperation> dataSourceOperationList;
		private T dataSourceManager;
		@SuppressWarnings("unchecked")
		public DefaultDataSourceManager(DataSourceProxy cglibDataSourceProxy ,List<DataSourceOperation> list) {
			this.cglibDataSourceProxy = cglibDataSourceProxy;
			this.dataSourceOperationList = Objects.isNull(list) ? new ArrayList<DataSourceOperation>() : list;
			this.dataSourceManager = Objects.isNull(this.cglibDataSourceProxy) ? null : (T)this.cglibDataSourceProxy.getProxy();
		}
		public T manager() {
			return dataSourceManager;
		}
		public List<DataSourceOperation> dataSource() {
			return dataSourceOperationList;
		}
		public boolean isEmpty() {
			return this.dataSourceOperationList.isEmpty();
		}
		public int size() {
			return this.dataSourceOperationList.size();
		}
		public DataSourceOperation next(Class<? extends T> cls) {
			for(int i = 0 ,len = dataSourceOperationList.size() ;i < len ;++i) {
				DataSourceOperation dso = dataSourceOperationList.get(i);
				if(cls.isAssignableFrom(dso.instance().getClass())) {
					return i + 1 >= len ? null : dataSourceOperationList.get(i+1);
				}
			}
			return null;
		}
		public boolean available() {
			return dataSourceManager != null;
		}
	}
	
	/**
	 * 	数据源接口实现对象操作接口
	 * @author huwei
	 *
	 */
	static private class DataSource implements DataSourceOperation{
		String springKey;
		Object instance;
		Class<?> interfaceClass;
		Integer order;
		Object[] args;
		Method method;
		Map<Long,Integer> invokeMarkMap = new ConcurrentHashMap<Long, Integer>();
		
		private DataSource(String springKey ,Object instance ,Class<?> interfaceClass){
			this.springKey = springKey;
			this.instance = instance;
			this.interfaceClass = interfaceClass;
		}
		public String springKey() {
			return springKey;
		}
		public Object instance() {
			return instance;
		}
		public Integer order() {
			return order;
		}
		public Object invokeCurrentMethod() {
			markInvoke();
			try {
				return method == null ? null : method.invoke(instance, args);
			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
				String msg = "invoke data source method failed ,that data source is : " + instance() 
				+ " ,that method is : " + currentMethod() 
				+ " ,that paramter is : " + (Objects.isNull(args) ? "NULL" : Arrays.toString(args));
				log.error(msg, e);
				throw new RuntimeException(msg,e);
			}
		}
		private void markInvoke() {
			synchronized (Thread.currentThread()) {
				Integer mark = invokeMarkMap.get(Thread.currentThread().getId());
				mark = mark == null ? 1 : ++mark;
				invokeMarkMap.put(Thread.currentThread().getId(), mark);
			}
		}
		public Class<?> interfaceClass() {
			return interfaceClass;
		}
		public boolean isInvoke() {
			return invokeCount() != 0;
		}
		public int invokeCount() {
			Integer mark = invokeMarkMap.get(Thread.currentThread().getId());
			return mark == null ? 0 : mark;
		}
		public void clearInvoke() {
			invokeMarkMap.remove(Thread.currentThread().getId());
		}
		@Override
		public Object[] currentParamters() {
			return args;
		}
		@Override
		public Method currentMethod() {
			return method;
		}
	}
}
